package com.segersten.kalaha;

import com.segersten.kalaha.domain.EventType
import com.segersten.kalaha.domain.Event
import java.util.logging.Logger
import groovy.json.*

class BoardSession implements Serializable {

	String sessionId
	Player player1
	Player player2
	GameType gameType
	Board board
	List events
	Actor nextActor = Actor.ENGINE
	Date lastUpdated = new Date()
	Integer roundDuration

	def addEvent = { EventType eventType, Object arg ->
		def event = Event.createEvent(getLastEvent(), eventType, arg)
		handleCreatedEvent(event)
		Actor actorOwningEvent = getActorOwningEvent(event)
		if (actorOwningEvent == nextActor || event.getType().isTimeout()) {
			return processEventQueue(event)
		} else { // something went wrong, log the state and send it to clients again
			Logger LOG = Logger.getLogger(getClass().getCanonicalName());
			LOG.severe("Unordered event " + event + " expected from actor " + nextActor + " but got " + actorOwningEvent + "\nEvents :" + events + "\nBoard : " + board);
			if (actorOwningEvent== Actor.PLAYER_1) {
				Channel.sendUpdateToClients(new BoardSessionJSON().toJSON(this), player1)
			}
			else if (actorOwningEvent== Actor.PLAYER_2) {
				Channel.sendUpdateToClients(new BoardSessionJSON().toJSON(this), player2)
			}
			return false
		}
	}

	// TODO remove this, handle like everything else in processEventQueue
	def handleCreatedEvent = { Event event ->
		if (events.size()==0) {
			if (event.type != EventType.GAME_CREATED ||
			event.arg ==null) {
				throw new IllegalArgumentException("First event type must be GAME_CREATED and must contain GameType as argument")
			}
			gameType = (GameType)event.arg
			Logger LOG = Logger.getLogger(getClass().getCanonicalName());
			LOG.info("Game started ${player1.name} - ${player2.name}")
		}
	}

	def processEventQueue = { Event event ->
		Logger LOG = Logger.getLogger(getClass().getCanonicalName());

		//LOG.info("NextActor ${nextActor}")
		//LOG.info("Event ${event}")

		def logic = new GameLogicContainer().get(gameType)
		def result = logic.notifyEvent(getLastEvent(), event, board)
		//LOG.info("Logic result ${event}")
		addEventInternal(event)
		if (result) {
			board = result.board
			nextActor = result.nextActor
			if (result.gameFinished == true) {
				handleGameFinished(event.type)
				return false
			} else {
				lastUpdated = new Date()
				Channel.sendUpdateToClients(new BoardSessionJSON().toJSON(this), player1, player2)
				return true
			}
		}
	}

	def handleGameFinished = { EventType eventType ->
		Logger LOG = Logger.getLogger(getClass().getCanonicalName());
		LOG.info("Game finished ${player1.name} - ${player2.name}")
		addEvent(EventType.GAME_FINISHED, null)
		nextActor = Actor.NONE

		int player1Points = board.player1score
		int player2Points = board.player2score

		def reasonFinished = FinishedGame.REASON_FINISHED
		if (eventType == EventType.PLAYER_1_TIMEOUT) {
			reasonFinished = FinishedGame.REASON_TIMED_OUT
			player1Points = -player1Points
			player2Points = 0
		} else if (eventType == EventType.PLAYER_2_TIMEOUT) {
			reasonFinished = FinishedGame.REASON_TIMED_OUT
			player1Points = 0
			player2Points = -player2Points
		}
		
		if (player1Points>player2Points) {
			player2Points = player2Points/2
		} else if (player1Points<player2Points) {
			player1Points = player1Points/2
		} else {
			player1Points = player1Points/2
			player2Points = player2Points/2
		}
		
		updatePlayerScore(player1,player1Points)
		updatePlayerScore(player2,player2Points)

		FinishedGame gf = new FinishedGame(reason:reasonFinished,
				sessionId:sessionId,
				player1:player1.userId,
				player1Name:player1.name,
				player2:player2.userId,
				player2Name:player2.name,
				player1Points:player1Points,
				player2Points:player2Points,
				endState:model2String(board),
				gameType:"KALAHA",
				startedTime:getStarted(),
				finishedTime:new Date())
		gf.save()

		SessionTicket ticket1 = SessionTicket.find("SELECT FROM SessionTicket st WHERE st.ownerId = :id", [id:player1.userId])
		ticket1.delete()

		SessionTicket ticket2 = SessionTicket.find("SELECT FROM SessionTicket st WHERE st.ownerId = :id", [id:player2.userId])
		ticket2.delete()

		BoardSessionCache bsc = new BoardSessionCache()
		bsc.remove(sessionId)
		
		Channel.sendUpdateToClients(new BoardSessionJSON().toJSON2(gf), player1, player2)
		
		LOG.info("Game finished DONE ${player1.name} - ${player2.name}")
	}

	def getLastEvent = {
		events.size()==0?null:events[events.size()-1]
	}

	def addEventInternal = { Event event ->
		events.add(event)
	}

	def getEventsInfo = {
		String res = "events:["
		events.each { Event event ->
			res += "${event.toString()} "
		}
		res += "]"
	}

	def getStarted = {
		if (events==null || events.size()==0) {
			return null
		} else {
			return events[0].created
		}
	}


	def getActorOwningEvent = { Event event ->
		switch (event.type) {
			case EventType.PLAYER_1_ACTION:
			case EventType.PLAYER_1_TIMEOUT:
				return Actor.PLAYER_1
				break
			case EventType.PLAYER_2_TIMEOUT:
			case EventType.PLAYER_2_ACTION:
				return Actor.PLAYER_2
				break
			case EventType.GAME_CREATED:
			case EventType.PLAYER_1_JOINED:
			case EventType.PLAYER_2_JOINED:
			case EventType.PLAYER_2_JOINED:
			case EventType.GAME_FINISHED:
				return Actor.ENGINE
			default:
				throw new IllegalArgumentException("Unknown type " + event.type)
		}
	}
	
	def updatePlayerScore = { Player player, int points ->
		if (player.type!="DBG") {
			updatePlayerScoreDay(player,points)
			updatePlayerScoreWeek(player,points)
			updatePlayerScoreTotal(player,points)
		}
	}
	
	def updatePlayerScoreDay = { Player player, int points ->
		PlayerScoreDay score = SessionTicket.find("SELECT FROM PlayerScoreDay sc WHERE sc.userId = :id", [id:player.userId])
		if (score==null) {
			score = new PlayerScoreDay(userId:player.userId,name:player.name,created:new Date())
		}
		score.score += points
		if (score.score<0) { score.score = 0 }
		score.lastUpdated = new Date()
		score.save()
	}
	
	def updatePlayerScoreWeek = { Player player, int points ->
		PlayerScoreWeek score = SessionTicket.find("SELECT FROM PlayerScoreWeek sc WHERE sc.userId = :id", [id:player.userId])
		if (score==null) {
			score = new PlayerScoreWeek(userId:player.userId,name:player.name,created:new Date())
		}
		score.score += points
		if (score.score<0) { score.score = 0 }
		score.lastUpdated = new Date()
		score.save()
	}
	
	def updatePlayerScoreTotal = { Player player, int points ->
		PlayerScoreTotal score = SessionTicket.find("SELECT FROM PlayerScoreTotal sc WHERE sc.userId = :id", [id:player.userId])
		if (score==null) {
			score = new PlayerScoreTotal(userId:player.userId,name:player.name,created:new Date())
		}
		score.score += points
		if (score.score<0) { score.score = 0 }
		score.lastUpdated = new Date()
		score.save()
	}
	
	def model2String = { Board board ->
		
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		Iterator<Integer> iter = board.getBuckets().iterator();
		while(iter.hasNext()) {
			Integer n = iter.next()
			sb.append(n)
			if (iter.hasNext()) {
				sb.append(",")
			}
		}
		sb.append("]");
		return sb.toString()
	}

}

